#include "GameModel.h"


GameModel::GameModel()
{
	this->figures = cloneFigures(StageParser::getInstance()->getGameStageData().getFigures());
	this->timeStep = (1.0f / 30.0f);
	this->velocityIterations = 6;
	this->positionIterations = 2;

	b2Vec2 g(0.0f, -10.0f);
	this->world = new b2World(g);
	this->scaleX = StageParser::getInstance()->getGameStageData().getPxWindowWidth() / StageParser::getInstance()->getGameStageData().getLUWindowWidth();
	this->scaleY = StageParser::getInstance()->getGameStageData().getPxWindowHigh() / StageParser::getInstance()->getGameStageData().getLUWindowHigh();
}

vector <Figure*> GameModel::cloneFigures(vector <Figure*> figuresParser) {
	vector <Figure*> toCloneFigures;

	for (std::vector<Figure*>::size_type i = 0; i != figuresParser.size(); i++) {
		toCloneFigures.push_back(figuresParser[i]->clone());
	}

	return toCloneFigures;
}



void GameModel::setLogicWaterLevel(unsigned int logicWaterLevel) {
	this->logicWaterLevel = logicWaterLevel;
}

void GameModel::initGround() {
	if (this->width == 0) {
		Log::getInstance()->write(Log::ERROR, "Imposible iniciar terreno, setear parametros.");
	}
	else {
		this->ground = new Terreno(this->hights, this->scaleX, this->width, *this->world);
	}
}

Position* GameModel::transformCoordBox2DToSDL(Position* position) {
	Position* newPosition = new Position();
	// PIXEL=LOGIC_UNIT*SCALE
	newPosition->setX(float32(position->getX()*scaleX));
	newPosition->setY(float32(((this->height - position->getY()*scaleY))));

	return newPosition;
}

void GameModel::initCircle(Figure* figure) {
	// Define the dynamic body. We set its position and call the body factory.
	b2BodyDef bodyDef;
	
	if (!figure->is_static())
		bodyDef.type = b2_dynamicBody;

	bodyDef.position.Set(figure->getX_position(), figure->getY_position());
	figure->setFigureBody(this->world->CreateBody(&bodyDef));
		
	// Cargo los puntos al poligono
	b2CircleShape circle;
	circle.m_radius = figure->getScale();

	// Define the dynamic body fixture.
	b2FixtureDef fixtureDef;
	fixtureDef.shape = &circle;

	// Densidad de una circunsferencia
	fixtureDef.density = figure->getMass() / (Constants::PI * figure->getScale() * figure->getScale());

	// Override the default friction.
	fixtureDef.friction = 0.3f;

	// Add the shape to the body.
	figure->getFigureBody()->CreateFixture(&fixtureDef);
}

float getRadRotation(float rotationAngle) {
	return float((rotationAngle / 180.0)*Constants::PI);
}

void GameModel::initFigures() {
	
	for (std::vector<Figure*>::size_type i = 0; i != this->figures.size(); i++) {
		if (isPoligon(figures[i])) {
			figures[i]->initPoints();
			initPolygon(figures[i]);
		} else if (isCircle(figures[i]))  {
			initCircle(figures[i]);
		}
		else {
			figures[i]->initPoints();
			initRectangle(figures[i]);
		}
	}

}

bool GameModel::isPoligon(Figure* figure) {
	if ((figure->getType() != 0) && (figure->getType() != 4)) {
		return true;
	}
	return false;
}

bool GameModel::isCircle(Figure* figure) {
	if (figure->getType() == 0) {
		return true;
	}
	return false;
}

void GameModel::initRectangle(Figure* figure) {
	// Define the dynamic body. We set its position and call the body factory.
	b2BodyDef bodyDef;

	// Box2d toma el angulo EN RADIANES y en sentido antihorario por eso < 0
	bodyDef.angle = getRadRotation(-figure->getInitialRotation());

	if (!figure->is_static())
		bodyDef.type = b2_dynamicBody;

	bodyDef.position.Set(figure->getX_position(), figure->getY_position());
	figure->setFigureBody(this->world->CreateBody(&bodyDef));

	b2Vec2* points = new b2Vec2[figure->getType()];

	// Seteo los vertices de la figura en el vector puntos para cargar
	// este al poligono
	for (std::vector<Position*>::size_type i = 0; i != figure->getPoints().size(); i++) {
		Position* box2DPositionVec = figure->getPoints()[i];
		points[i].Set(box2DPositionVec->getX(), box2DPositionVec->getY());
	}

	// Cargo los puntos al poligono
	b2PolygonShape polygon;
	polygon.Set(points, figure->getType());

	// Define the dynamic body fixture.
	b2FixtureDef fixtureDef;
	fixtureDef.shape = &polygon;

	// Aproximo con el area de una circunsferencia en donde el poligono este circunscripto.
	fixtureDef.density = figure->getMass() / (Constants::PI * figure->getScale() * figure->getScale());

	// Override the default friction.
	fixtureDef.friction = 0.3f;

	// Add the shape to the body.
	figure->getFigureBody()->CreateFixture(&fixtureDef);
}


void GameModel::initPolygon(Figure* figure) {
	// Define the dynamic body. We set its position and call the body factory.
	b2BodyDef bodyDef;
	
	// Box2d toma el angulo EN RADIANES y en sentido antihorario por eso < 0
	bodyDef.angle = getRadRotation(-figure->getInitialRotation());

	if (!figure->is_static())
		bodyDef.type = b2_dynamicBody;
	
	bodyDef.position.Set(figure->getX_position(), figure->getY_position());
	figure->setFigureBody(this->world->CreateBody(&bodyDef));
	

	b2Vec2* points = new b2Vec2[figure->getType()];
	
	// Seteo los vertices de la figura en el vector puntos para cargar
	// este al poligono
	for (std::vector<Position*>::size_type i = 0; i != figure->getPoints().size(); i++) {
		Position* box2DPositionVec = figure->getPoints()[i];
		points[i].Set(box2DPositionVec->getX(), box2DPositionVec->getY());
	}

	// Cargo los puntos al poligono
	b2PolygonShape polygon;
	polygon.Set(points, figure->getType());	
		
	// Define the dynamic body fixture.
	b2FixtureDef fixtureDef;
	fixtureDef.shape = &polygon;
	
	// Aproximo con el area de una circunsferencia en donde el poligono este circunscripto.
	fixtureDef.density = figure->getMass() / (Constants::PI * figure->getScale() * figure->getScale());

	// Override the default friction.
	fixtureDef.friction = 0.3f;

	// Add the shape to the body.
	figure->getFigureBody()->CreateFixture(&fixtureDef);
}

void GameModel::setParametersToInitGround(const float* hights, unsigned int width, unsigned int height) {
	this->hights = hights;	
	this->width = width;
	this->height = height;
}

void GameModel::step() {
	(*this->world).Step(timeStep, velocityIterations, positionIterations);
	updateFiguresPosition();
}

void GameModel::updateFiguresPosition() {
	for (std::vector<Figure*>::size_type i = 0; i != this->figures.size(); i++) {
		updatePosition(figures[i]);
	}
}

void GameModel::updatePosition(Figure* figure) {
	float angleLog = figure->getFigureBody()->GetAngle();
	b2Vec2 position = figure->getFigureBody()->GetPosition();
	
	// figure->getFigureBody()->GetAngle(): RADIANES
	figure->setRotation((figure->getFigureBody()->GetAngle()) * 180 / Constants::PI);

	if (position.y > -25.0)  {
		if (position.y < this->logicWaterLevel) {
			b2Vec2 velocity(0.0f, -5.0f);
			figure->getFigureBody()->SetLinearVelocity(velocity);
			figure->getFigureBody()->SetGravityScale(0.5f);
		}
		
		figure->set_position(position.x, position.y);
	}
	else {
		this->world->DestroyBody(figure->getFigureBody());
		// Seteo una figura estatica bajo tierra.
		b2BodyDef bodyDef;
		bodyDef.position.Set(figure->getX_position(), figure->getY_position());
		figure->setFigureBody(this->world->CreateBody(&bodyDef));
	}
	
	figure->initPoints();
}

vector <Figure*> GameModel::getFigures() {
	return this->figures;
}

void GameModel::setFigures(vector <Figure*> figures) {
	this->figures = figures;
}

GameModel::~GameModel()
{
	for (std::vector<Figure*>::size_type i = 0; i != this->figures.size(); i++) {
		delete figures[i];
	}
}
